//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Core.h".
//-------------------------------------------------------------------

#ifndef _UXS_PHYSICS_DIMENSION_H
#define _UXS_PHYSICS_DIMENSION_H

#pragma once

#include "../CompileTime/TypeList.h"
#include "../CompileTime/TFractional.h"

namespace UX_STL
{
namespace UXTypes
{

	//
	// Physics Dimension enum
	//

	struct EPhysicsDimension
	{
		enum type
		{
			// SI
			SECONDS,		// time
			KILOGRAMS,		// mass
			METERS,			// length
			AMPERES,		// electric current
			KELVINS,		// temperature
			MOLES,			// amount of substance
			CANDELAS,		// luminous intensity

			// non SI
			CURRENCY,		// monetary unit
			BITS,			// unit of information

			_COUNT
		};
	};



	//
	// Physics Dimension
	//

	template <EPhysicsDimension::type Dim, typename FracPower = CompileTime::Fractional32<0>::type >
	struct TPhysicsDimension
	{
	// types
	public:
		typedef TPhysicsDimension< Dim, FracPower >	Self;
		typedef FracPower							POWER;
	
		static const EPhysicsDimension::type		DIMENSION	= Dim;


		template <typename T>
		struct IsSame {
			static const bool	value = (DIMENSION == T::DIMENSION);
		};

		template <typename T>
		struct Mul {
			typedef TPhysicsDimension< DIMENSION, typename POWER::Add< T::POWER >::type >	type;
		};

		template <typename T>
		struct MulIfSame {
			typedef typename SwitchType< IsSame<T>::value, typename Mul<T>::type, Self >::type	type;
		};

		struct Inverse {
			typedef TPhysicsDimension< DIMENSION, typename POWER::Neg::type >	type;
		};

		template <typename ToFracPower>
		struct Power {
			typedef TPhysicsDimension< DIMENSION, typename POWER::Mul< ToFracPower >::type >	type;
		};

		template <typename T>
		struct Equal {
			static const bool	value = IsSame<T>::value and POWER::Equal<T::POWER>::value;
		};


	// methods
	public:
		static string ToString ()
		{
			static const char *	shortTypeNames[] = {
				"s", "kg", "m", "A", "K", "mol", "cd", "$", "bit"
			};

			STATIC_ASSERTE( COUNT_OF(shortTypeNames) == EPhysicsDimension::_COUNT );

			if ( POWER::IsZero::value )
				return string();

			return ( string( shortTypeNames[ DIMENSION ] ) << '^' << POWER::ToString() );
		}
	};



	//
	// Physics Dimensions List
	//

	template <typename DimensionsTypeList>
	struct TPhysicsDimensionsList
	{
	// checks
		STATIC_ASSERTE( DimensionsTypeList::Length::value == EPhysicsDimension::_COUNT );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::SECONDS   >::type::DIMENSION == EPhysicsDimension::SECONDS   );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::KILOGRAMS >::type::DIMENSION == EPhysicsDimension::KILOGRAMS );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::METERS    >::type::DIMENSION == EPhysicsDimension::METERS    );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::AMPERES   >::type::DIMENSION == EPhysicsDimension::AMPERES   );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::KELVINS   >::type::DIMENSION == EPhysicsDimension::KELVINS   );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::MOLES     >::type::DIMENSION == EPhysicsDimension::MOLES     );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::CANDELAS  >::type::DIMENSION == EPhysicsDimension::CANDELAS  );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::CURRENCY  >::type::DIMENSION == EPhysicsDimension::CURRENCY  );
		STATIC_ASSERTE( DimensionsTypeList::Get< EPhysicsDimension::BITS      >::type::DIMENSION == EPhysicsDimension::BITS      );


	// types
	protected:
		template <typename Dim>
		struct _MulDimFunc
		{
			template <typename L, typename R>
			struct Op { typedef CompileTime::TypeList< L, R >	type; };

			template <typename T, usize Index>
			struct Process { typedef typename T::MulIfSame< Dim >::type	type; };

			typedef void	init_type;
		};

		template <typename Dims>
		struct _MulFunc
		{
			template <typename L, typename R>
			struct Op { typedef CompileTime::TypeList< L, R >	type; };

			template <typename T, usize Index>
			struct Process
			{
				typedef T															left;
				typedef typename Dims::Get< (EPhysicsDimension::type)Index >::type	right;

				STATIC_ASSERTE( left::IsSame< right >::value );

				typedef typename left::Mul< right >::type	type;
			};

			typedef void	init_type;
		};
	
		template <typename ToFracPower>
		struct _PowerFunc
		{	
			template <typename L, typename R>
			struct Op { typedef CompileTime::TypeList< L, R >	type; };

			template <typename T, usize Index>
			struct Process { typedef typename T::Power< ToFracPower >::type	type; };

			typedef void	init_type;
		};

		struct _IsNonDimFunc
		{	
			template <bool L, bool R>
			struct Op { static const bool	value = L and R; };

			template <typename T, usize Index>
			struct Process { static const bool	value = T::POWER::IsZero::value; };

			static const bool	init_value = true;
		};

		struct _ToStringFunc
		{
			const uni_c_string	divider;
			string				result;
			usize				prevLength;

			_ToStringFunc (uni_c_string divider) : divider(divider), prevLength(0)
			{}

			template <typename T, usize Index>
			void Process ()
			{
				result << T::ToString();
				
				if ( result.Length() != prevLength )
				{
					result << divider;
					prevLength = result.Length();
				}
			}
		};


	public:
		typedef bool											_is_physics_dimensions_list;
		typedef DimensionsTypeList								DimensionsTypeList_t;
		typedef TPhysicsDimensionsList< DimensionsTypeList >	Self;

		template <typename Dim>
		struct _MulDim {
			typedef TPhysicsDimensionsList< typename DimensionsTypeList::TypeForEach< _MulDimFunc< Dim > >::type >		type;
		};

		template <typename Dims>
		struct Add {
			STATIC_ASSERTE( typename Dims::_is_physics_dimensions_list(true) );
			STATIC_ASSERTE( Self::Equal< Dims >::value );
			typedef Self	type;
		};

		template <typename Dims>
		struct Sub {
			STATIC_ASSERTE( typename Dims::_is_physics_dimensions_list(true) );
			STATIC_ASSERTE( Self::Equal< Dims >::value );
			typedef Self	type;
		};
	
		template <typename Dims>
		struct Mul {
			STATIC_ASSERTE( typename Dims::_is_physics_dimensions_list(true) );
			typedef TPhysicsDimensionsList< typename DimensionsTypeList::TypeForEach< _MulFunc< Dims > >::type >		type;
		};

		template <typename Dims>
		struct Div {
			STATIC_ASSERTE( typename Dims::_is_physics_dimensions_list(true) );
			typedef typename Mul< typename Dims::Inverse::type >::type		type;
		};
	
		template <typename ToFracPower>
		struct Power {
			typedef TPhysicsDimensionsList< typename DimensionsTypeList::TypeForEach< _PowerFunc< ToFracPower > >::type >		type;
		};

		struct Inverse {
			typedef typename Power< CompileTime::Fractional32<-1>::type >::type	type;
		};

		template <EPhysicsDimension::type Index>
		struct Get {
			typedef typename DimensionsTypeList::Get< Index >::type		type;
		};

		template <typename Dims>
		struct Equal {
			static const bool	value = DimensionsTypeList::Equal< Dims::DimensionsTypeList_t >::value;
		};

		struct IsNonDimensional {
			static const bool	value = DimensionsTypeList::StaticForEach< _IsNonDimFunc, bool >::value;
		};


	// methods
	public:
		static string ToString (uni_c_string divider = "\n")
		{
			_ToStringFunc	func( divider );

			DimensionsTypeList::ForEach( func );
		
			if ( not func.result.Empty() )
				func.result.EraseFromBack( divider.Length() );

			return func.result;
		}
	};



	//
	// Simplified Physics Dimensions List
	//

	namespace _types_hidden_
	{
		template <	int SecondsNom = 0,		uint SecondsDenom = 1,
					int KilogramsNom = 0,	uint KilogramsDenom = 1,
					int MetersNom = 0,		uint MetersDenom = 1,
					int AmpersNom = 0,		uint AmpersDenom = 1,
					int KelvinsNom = 0,		uint KelvinsDenom = 1,
					int MolesNom = 0,		uint MolesDenom = 1,
					int CandelasNom = 0,	uint CandelsDenom = 1,
					int CurrencyNom = 0,	uint CurrencyDenom = 1,
					int BitsNom = 0,		uint BitsDenom = 1
				 >
		struct PhysDimList
		{
		// types
		private:
			template <typename Dims>
			struct _From {
				typedef typename Dims::DimensionsTypeList_t	tl;

				typedef PhysDimList<
							tl::Get< EPhysicsDimension::SECONDS   >::type::POWER::N,	tl::Get< EPhysicsDimension::SECONDS   >::type::POWER::D,
							tl::Get< EPhysicsDimension::KILOGRAMS >::type::POWER::N,	tl::Get< EPhysicsDimension::KILOGRAMS >::type::POWER::D,
							tl::Get< EPhysicsDimension::METERS    >::type::POWER::N,	tl::Get< EPhysicsDimension::METERS    >::type::POWER::D,
							tl::Get< EPhysicsDimension::AMPERES   >::type::POWER::N,	tl::Get< EPhysicsDimension::AMPERES   >::type::POWER::D,
							tl::Get< EPhysicsDimension::KELVINS   >::type::POWER::N,	tl::Get< EPhysicsDimension::KELVINS   >::type::POWER::D,
							tl::Get< EPhysicsDimension::MOLES     >::type::POWER::N,	tl::Get< EPhysicsDimension::MOLES     >::type::POWER::D,
							tl::Get< EPhysicsDimension::CANDELAS  >::type::POWER::N,	tl::Get< EPhysicsDimension::CANDELAS  >::type::POWER::D,
							tl::Get< EPhysicsDimension::CURRENCY  >::type::POWER::N,	tl::Get< EPhysicsDimension::CURRENCY  >::type::POWER::D,
							tl::Get< EPhysicsDimension::BITS      >::type::POWER::N,	tl::Get< EPhysicsDimension::BITS      >::type::POWER::D >	type;
			};

		public:
			typedef PhysDimList< SecondsNom,	SecondsDenom,
								 KilogramsNom,	KilogramsDenom,
								 MetersNom,		MetersDenom,
								 AmpersNom,		AmpersDenom,
								 KelvinsNom,	KelvinsDenom,
								 MolesNom,		MolesDenom,
								 CandelasNom,	CandelsDenom,
								 CurrencyNom,	CurrencyDenom,
								 BitsNom,		BitsDenom		>	Self;

			typedef TPhysicsDimensionsList<
						CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::SECONDS,    typename CompileTime::Fractional32< SecondsNom, SecondsDenom >::type >,
						  CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::KILOGRAMS, typename CompileTime::Fractional32< KilogramsNom, KilogramsDenom >::type >,
						    CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::METERS,   typename CompileTime::Fractional32< MetersNom, MetersDenom >::type >,
						      CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::AMPERES, typename CompileTime::Fractional32< AmpersNom, AmpersDenom >::type >,
						        CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::KELVINS, typename CompileTime::Fractional32< KelvinsNom, KelvinsDenom >::type >,
						          CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::MOLES,   typename CompileTime::Fractional32< MolesNom, MolesDenom >::type >,
						            CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::CANDELAS, typename CompileTime::Fractional32< CandelasNom, CandelsDenom >::type >,
						              CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::CURRENCY, typename CompileTime::Fractional32< CurrencyNom, CurrencyDenom >::type >,
						                CompileTime::TypeList< TPhysicsDimension< EPhysicsDimension::BITS,    typename CompileTime::Fractional32< BitsNom, BitsDenom >::type >,
										  void
						> > > > > > > > > >			dim_t;

			template <typename Dims>
			struct Add {
				typedef typename _From< typename dim_t::Add< Dims::dim_t >::type >::type	type;
			};

			template <typename Dims>
			struct Sub {
				typedef typename _From< typename dim_t::Sub< Dims::dim_t >::type >::type	type;
			};
	
			template <typename Dims>
			struct Mul {
				typedef typename _From< typename dim_t::Mul< Dims::dim_t >::type >::type	type;
			};

			template <typename Dims>
			struct Div {
				typedef typename _From< typename dim_t::Div< Dims::dim_t >::type >::type	type;
			};
	
			template <typename ToFracPower>
			struct Power {
				typedef typename _From< typename dim_t::Power< ToFracPower >::type >::type	type;
			};

			template <EPhysicsDimension::type Index>
			struct Get {
				typedef typename dim_t::Get< Index >::type		type;
			};

			struct Inverse {
				typedef typename _From< typename dim_t::Inverse::type >::type	type;
			};

			template <typename Dims>
			struct Equal {
				static const bool	value = dim_t::Equal< Dims::dim_t >::value;
			};

			struct IsNonDimensional {
				static const bool	value = dim_t::IsNonDimensional::value;
			};


		protected:
			template <typename Dim>
			struct _MulDim {
				typedef typename _From< typename dim_t::_MulDim< Dim >::type >::type	type;
			};


		// methods
		public:
			static string ToString (uni_c_string divider = "\n")
			{
				return dim_t::ToString( divider );
			}
		};

	}	// _types_hidden_
	


	//
	// Default Physics Dimensions List
	//

	struct DefaultPhysicsDimensionsList : _types_hidden_::PhysDimList<>
	{
		template <EPhysicsDimension::type DimType, isize PowNum = 1, isize PowDenom = 1>
		struct Create
		{
			typedef typename Self::_MulDim< TPhysicsDimension< DimType,
						typename CompileTime::Fractional32< PowNum, PowDenom >::type::Simplified > >::type	type;
		};

		struct CreateNonDimensional {
			typedef Self	type;
		};
	};


	//
	// Default Physics Dimensions
	//

	struct DefaultPhysicsDimensions
	{
		// base //
		typedef DefaultPhysicsDimensionsList::CreateNonDimensional::type					NonDimensional;	// 
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::SECONDS >::type	Second;			// s
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::KILOGRAMS >::type	Kilogram;		// kg
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::METERS >::type		Meter;			// m
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::AMPERES >::type	Amper;			// A
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::KELVINS >::type	Kelvin;			// K
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::MOLES >::type		Mole;			// mol
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::CANDELAS >::type	Candela;		// cd
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::CURRENCY >::type	Currency;		// $
		typedef DefaultPhysicsDimensionsList::Create< EPhysicsDimension::BITS >::type		Bit;			// bit
		
		// compound //
		typedef Meter::Power< CompileTime::Fractional32< 2 >::type >::type					SquareMeter;			// m^2
		typedef Meter::Power< CompileTime::Fractional32< 3 >::type >::type					CubicMeter;				// m^3
		typedef Meter::Div< Second >::type													MeterPerSecond;			// m / s
		typedef MeterPerSecond::Div< Second >::type											MeterPerSquareSecond;	// m / s^2
		typedef Kilogram::Div< Second >::type												KilogramPerSecond;		// kg / s
		typedef MeterPerSecond::Mul< Kilogram >::type										KilogramMeterPerSecond;	// kg * m / s
		typedef Kilogram::Div< CubicMeter >::type											KilogramPerCubicMeter;	// kg / m^3
		typedef	Kilogram::Mul< MeterPerSquareSecond >::type									Newton;					// N = kg * m / s^2
		typedef Newton::Mul< Meter >::type													Joule;					// J = kg * (m / s)^2
		typedef Kilogram::Div< Meter::Mul< Second::Mul< Second >::type >::type >::type		Pascal;					// Pa = kg / (m * s^2)
		typedef Second::Inverse::type														Hertz;					// Hz = 1 / s
		typedef Joule::Div< Second >::type													Watt;					// W = J / s
		typedef Amper::Mul< Second >::type													Coulomb;				// C = A * s
		typedef Joule::Div< Coulomb >::type													Volt;					// V = J / C
		typedef Volt::Div< Amper >::type													Ohm;					// Ohm = V / A
		typedef Coulomb::Div< Volt >::type													Farad;					// F = C / V
		typedef Volt::Mul< Second >::type													Weber;					// Wb = V * s
		typedef Weber::Div< Amper >::type													Henry;					// H = Wb / A
		typedef Weber::Div< SquareMeter >::type												Tesla;					// T = Wb / m^2
		typedef Ohm::Inverse::type															Siemens;				// S = 1 / Ohm
		typedef Candela																		Lumen;					// lm = cd * (sr)
		typedef	Lumen::Div< SquareMeter >::type												Lux;					// lx = lm / m^2
		typedef Amper::Div< Meter >::type													AmperPerMeter;			// A/m
		typedef Kilogram::Div< Mole >::type													KilogramPerMole;		// kg/mol
	};


}	// UXTypes
}	// UX_STL

#endif	// _UXS_PHYSICS_DIMENSION_H